// Max12Exporter.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include <vector>

#include "Max12Exporter.h"
#include <algorithm>
#include "Texture.h"
#include "sphere.h"

using namespace MiniBall;
namespace NBE
{
	
Max12Exporter::Max12Exporter() :
	m_igame( 0 ),
	m_fh( 0 )
{
}

Max12Exporter::~Max12Exporter()
{
	deinit();
}

void Max12Exporter::deinit()
{
	m_ofs.close();

	if ( m_igame != 0 )
	{
		m_igame->ReleaseIGame();
		m_igame = 0;
	}
}

int Max12Exporter::DoExport( const TCHAR* name, ExpInterface* ei, Interface* i, BOOL suppressprompts, DWORD options )
{
	try
	{
		const bool exportselected = 0 != (options & SCENE_EXPORT_SELECTED); // export only selected objects

		// open file for writing
		m_ofs.open(name,std::ios_base::binary);
 
		m_igame = GetIGameInterface();
		if ( !m_igame )
			throw std::exception( "Failed to initialize IGame interface" );

		IGameConversionManager* cm = GetConversionManager();
		cm->SetCoordSystem(IGameConversionManager::IGAME_OGL);
	 

		m_igame->InitialiseIGame( exportselected );

		 

		int count_topLevelNode = m_igame->GetTopLevelNodeCount(); 

		MeshData.resize(sizeof(Mesh));
		
		Mesh* MeshFile = new(&MeshData[0]) Mesh();

		for(int i = 0; i < count_topLevelNode; ++i)
		{
			IGameNode* node = m_igame->GetTopLevelNode(i);
			if(node)
			{
				ExportMat(node);
			}
		}

		size_t numOfMat = matVec.size();

		MeshFile->matArray =  0;
		MeshFile->materialNum = (int)numOfMat;
 

	 

		/// now export the objects, having their own vertices and indices, so the num will not be too large

		for(int i = 0; i < count_topLevelNode; ++i)
		{
			IGameNode* node = m_igame->GetTopLevelNode(i);
			if(node)
			{
				ExportObjectsFromNode(node);
			}
		}

		MeshFile->objectNum = (int)objectVec.size();
		 
		MeshFile->objectArray =  0;
		 
		unsigned int verNum = (unsigned int)VertexData.size() / sizeof(Vertex);
		
		Point* vertexArray = new Point[verNum];
		for (size_t i = 0; i < verNum; ++i)
		{
			//get the vertex
			vertexArray[i] = Point( ((Vertex*)(&VertexData[0] + i*sizeof(Vertex)))->pos );
		}
		Sphere theSphere= Sphere::miniBall(vertexArray,verNum);
		Point3 c = Point3(theSphere.center.getX(),theSphere.center.getY(),theSphere.center.getZ());
	    MeshFile->center = c;
		MeshFile->radius = theSphere.radius;
		
		delete[] vertexArray;

		//////////////////////////////////////////////////////////////////////////
		
		size_t headerOffsets[9];
		headerOffsets[0] = 0;
		headerOffsets[1] = headerOffsets[0] + MeshData.size();
		headerOffsets[2] = headerOffsets[1] + MaterialData.size();
		headerOffsets[3] = headerOffsets[2] + ObjectData.size();
		headerOffsets[4] = headerOffsets[3] + VertexData.size();
		headerOffsets[5] = headerOffsets[4] + IndexData.size(); 
		headerOffsets[6] = headerOffsets[5] + BatchData.size();
		headerOffsets[7] = headerOffsets[6] + texMapData.size();
		headerOffsets[8] = headerOffsets[7] + stringData.size();


		size_t dataLength[9]={MeshData.size(),MaterialData.size(),
			ObjectData.size(),VertexData.size(),IndexData.size(), BatchData.size(),
			texMapData.size(),stringData.size(),textureData.size()};

		size_t totalsize =  sizeof(headerOffsets) + headerOffsets[8] + dataLength[8];

		m_ofs.write((const char*)&totalsize,sizeof(size_t));
		m_ofs.write((const char*)headerOffsets,sizeof(headerOffsets));
		 
		m_ofs.write((const char*)&MeshData[0], dataLength[0]);
		m_ofs.write((const char*)&MaterialData[0], dataLength[1]);
		m_ofs.write((const char*)&ObjectData[0], dataLength[2]);
		m_ofs.write((const char*)&VertexData[0], dataLength[3]);
		m_ofs.write((const char*)&IndexData[0], dataLength[4]);
		m_ofs.write((const char*)&BatchData[0], dataLength[5]);
		m_ofs.write((const char*)&texMapData[0], dataLength[6]);
		m_ofs.write((const char*)&stringData[0], dataLength[7]);
		m_ofs.write((const char*)&textureData[0], dataLength[8]);
		// release initialized stuff
		deinit();
	}
	catch ( std::exception& e )
	{
		TSTR tstr( e.what() );
		MessageBox( i->GetMAXHWnd(), tstr, _T("Export Error"), MB_OK|MB_ICONERROR );
		deinit();
	}
	return TRUE;
}

void Max12Exporter::outputFacesByMesh(IGameNode* node)
{
	IGameMesh* mesh = (IGameMesh*)node->GetIGameObject();
	if(mesh->InitializeData())
	{
		//face from mat
		Tab<int> tabMtlIDs = mesh->GetActiveMatIDs();

		if (tabMtlIDs.Count() > 0)
		{
			for(int m = 0; m< tabMtlIDs.Count(); ++m)
			{
				Tab<FaceEx*> tabFacesByMatID = mesh->GetFacesFromMatID(tabMtlIDs[m]);
				//output the usemtl
				if(tabFacesByMatID.Count() > 0)
				{
					IGameMaterial* pMaterial = mesh->GetMaterialFromFace(tabFacesByMatID[0]);
					if(pMaterial)
					{
						std::string line = std::string(pMaterial->GetMaterialName());
						int usemtlMark[3] = {-1,(int)line.size(),0};//indicate the engien , hey I will use this material 
						m_ofs.write((const char*)usemtlMark,sizeof(int) * 3);
						m_ofs.write((const char*)line.c_str(),line.size());
					}
					else
					{
						//no material !!!
						int usemtlMark[3] = {-1,0,0};//indicate the engine , no mat 
						m_ofs.write((const char*)usemtlMark,sizeof(int) * 3);
					}
				}

				

				for(int j = 0; j< tabFacesByMatID.Count(); ++j)
				{
					//face
					unsigned int p[3]={0};
					for(int k = 0; k<3; ++k)
					{
						FaceEx* face = tabFacesByMatID[j];
						if(face)
						{
							Point3 WorldVertex = mesh->GetVertex(face->vert[k]);
							GMatrix WorldToLocal =  node->GetWorldTM().Inverse();
							Point3 LocalVertex = WorldToLocal.ExtractMatrix3() * WorldVertex;

							Vertex v;
							v.pos = LocalVertex;// mesh->GetVertex( face->vert[k] );
							v.uv = mesh->GetTexVertex( face->texCoord[k] );
							v.normal = mesh->GetNormal( face->norm[k] );

							//v.uv.y = 1 - v.uv.y;

							p[k]  = (unsigned int)vertexExist(v);
			 
						}
		
						//indexVec.push_back(idx);
					}
					
					 
					m_ofs.write((const char*)p,sizeof(unsigned int) * 3);
					
				}
			}
		}
	}	
}

void Max12Exporter::ExportObjectsFromNode(IGameNode* node)
{	
	int tp = node->GetIGameObject()->GetIGameType();
	switch(tp)
	{
 	case IGameObject::IGAME_UNKNOWN: break;
 	case IGameObject::IGAME_LIGHT:	 break;
    case IGameObject::IGAME_MESH: ExportMeshFromNode(node);	 break;
 	case IGameObject::IGAME_SPLINE:	 break;
 	case IGameObject::IGAME_CAMERA:	 break;
    case IGameObject::IGAME_HELPER:	ExportMeshFromNode(node);	 break; // like a dummy transparent obj
  	case IGameObject::IGAME_BONE:/*	ExportBones(node); */break;//I export bones before export any skin meshes , so no need to export again
  	case IGameObject::IGAME_IKCHAIN: break;
  	case IGameObject::IGAME_XREF:	 break;
	default:
		throw std::exception( "Cant recognize this node type to export material" );
	}
}

int Max12Exporter::ExportMeshFromNode(IGameNode* node) // includes materials
{
	// a IGame helper can also call this function
	IGameMesh* mesh = (IGameMesh*)node->GetIGameObject();

	size_t startOffset = ObjectData.size();
	ObjectData.resize((objectVec.size() + 1) * sizeof(Object));
	Object* pObj = new(&ObjectData[0] + startOffset) Object();
	objectVec.push_back(pObj);

	pObj->objectName =  addStringToData( node->GetName() );
	pObj->parent = -1;

	if(node->GetNodeParent())
	{
		pObj->parent = getObjectByName(node->GetNodeParent()->GetName());
	}

 
	 
	GMatrix	ParentWorldTM;
	IGameNode* parent = node->GetNodeParent();
	if( parent!= 0)
	{
		ParentWorldTM = parent->GetWorldTM();
	}
	else 
	{
		ParentWorldTM.SetIdentity();
	}

	GMatrix WorldToParent = ParentWorldTM.Inverse();
	GMatrix	LocalTM = node->GetWorldTM() * WorldToParent;

	Point3 pos = LocalTM.Translation();
	Quat rot = LocalTM.Rotation();
	Point3 scale = LocalTM.Scaling();
	 
	pObj->localTM = LocalTM;

	//////////////////////////////////////////////////////////////////////////
	if(mesh->InitializeData())
	{
		//face from mat
		Tab<int> tabMtlIDs = mesh->GetActiveMatIDs();
		if (tabMtlIDs.Count() > 0)
		{
			pObj->batchNum = tabMtlIDs.Count();

			for(int m = 0; m< tabMtlIDs.Count(); ++m)
			{
				Tab<FaceEx*> tabFacesByMatID = mesh->GetFacesFromMatID(tabMtlIDs[m]);

				IGameMaterial* pMaterial = mesh->GetMaterialFromFace(tabFacesByMatID[0]);
				if(pMaterial)
				{
					//create a batch for one material
					size_t startOffset = BatchData.size();
					BatchData.resize(BatchData.size() + sizeof(Batch));
					Batch* pbat = new(&BatchData[0] + startOffset) Batch();
					pbat->pMtl =  getMaterialIndexing(pMaterial) * sizeof(Material) ;
				
					if (m == 0)
					{
						pObj->batchVec = startOffset;
					}
					pbat->startIndex = (unsigned int)indexVec.size();

				for(int j = 0; j< tabFacesByMatID.Count(); ++j)
				{
					//face  3 vertices
					for(int k = 0; k<3; ++k)
					{
						FaceEx* face = tabFacesByMatID[j];

						Point3 WorldVertex = mesh->GetVertex(face->vert[k]);
						GMatrix WorldToLocal =  node->GetWorldTM().Inverse();
						Point3 LocalVertex = WorldToLocal.ExtractMatrix3() * WorldVertex;
						


						Vertex v;
						v.pos = LocalVertex;//mesh->GetVertex( face->vert[k] );
						v.uv = mesh->GetTexVertex( face->texCoord[k] );
						v.normal = mesh->GetNormal( face->norm[k] );
						indexVec.push_back((unsigned int)vertexExist(v));

					}
				}
					pbat->size = (unsigned int)indexVec.size() - pbat->startIndex;
				
				}

			}		
		}
	}

	 

	pObj->VertexNum = (unsigned int)vertexVec.size();
	pObj->IndexNum = (unsigned int)indexVec.size();

	{
		size_t startOffset = VertexData.size() ;
		size_t lenghtOfVertexVec = vertexVec.size() * sizeof(Vertex);
		VertexData.resize(VertexData.size() + lenghtOfVertexVec);
		pObj->verticesList = startOffset ;
		memcpy(&VertexData[startOffset],&vertexVec[0],lenghtOfVertexVec);
	}

	//indices
	{
		size_t startOffset = IndexData.size() ;
		size_t lenghtOfIndexVec = indexVec.size() * sizeof(unsigned int);
		IndexData.resize(IndexData.size()  + lenghtOfIndexVec);
		pObj->indicesList =  startOffset ;
		memcpy(&IndexData[startOffset],&indexVec[0],lenghtOfIndexVec);
	}


	//outputFacesByMesh(node);

	vertexVec.clear();
	indexVec.clear();

	//all children meshes
	int childCount = node->GetChildCount();
	 
 
	

	for(int j = 0; j < childCount; ++j)
	{
		ExportObjectsFromNode(node->GetNodeChild(j));
	}

	


	return TRUE;
}

int Max12Exporter::ExportMat(IGameNode* node) 
{
	int tp = node->GetIGameObject()->GetIGameType();
	if(tp!= IGameObject::IGAME_MESH && tp != IGameObject::IGAME_HELPER)
		return -1;

	IGameMesh* mesh = (IGameMesh*)node->GetIGameObject();

	if(mesh->InitializeData())
	{
		//face from mat
		Tab<int> tabMtlIDs = mesh->GetActiveMatIDs();
		if (tabMtlIDs.Count() > 0)
		{
			//1st pass
			for(int m = 0; m< tabMtlIDs.Count(); ++m)
			{
				Tab<FaceEx*> tabFacesByMatID = mesh->GetFacesFromMatID(tabMtlIDs[m]);
				for(int j = 0; j< tabFacesByMatID.Count(); ++j)
				{
					//material lib
					IGameMaterial* pMaterial = mesh->GetMaterialFromFace(tabFacesByMatID[j]);

					if(pMaterial >0 && !matExist(pMaterial))
					{

						matVec.push_back(pMaterial);
						outputMaterialInfo(pMaterial);

						int subMaterialCount = pMaterial->GetSubMaterialCount();
						for (int k = 0; k < subMaterialCount; ++k)
						{
							IGameMaterial* subMat = pMaterial->GetSubMaterial(k);
							if (subMat > 0)
							{
								matVec.push_back(subMat);
								outputMaterialInfo(subMat);
							}
						}
						
					}


				}
			}		
		}


	}		


	//all children meshes mat should be exported
	int childCount = node->GetChildCount();
	for(int j = 0; j < childCount; ++j)
	{
		ExportMat(node->GetNodeChild(j));
	}

	return TRUE;
}

void Max12Exporter::outputMaterialInfo(IGameMaterial* mat)
{
	size_t startOffset = MaterialData.size();
	MaterialData.resize(MaterialData.size() + sizeof(Material));
	Material* pNewMat = new(&MaterialData[0] + startOffset) Material();

	pNewMat->matName = addStringToData(mat->GetMaterialName());
    int numoftex = pNewMat->texMapVecSize = mat->GetNumberOfTextureMaps();
	 
	for (int i = 0; i < numoftex; ++i)
	{
		IGameTextureMap* mp = mat->GetIGameTextureMap(i);

		size_t startOffset = texMapData.size();
		texMapData.resize(texMapData.size() + sizeof(TexMap));
		TexMap* newTexMap = new(&texMapData[0] + startOffset) TexMap();
		
		if(i == 0)
		{
			pNewMat->texMapVec = startOffset;
		}
		char* nm = mp->GetBitmapFileName();
		if(nm)
		{
			loadTexFromFile( nm, newTexMap, textureData);
			
			std::string tenmpsrt = std::string(nm);
			tenmpsrt  = tenmpsrt.substr(tenmpsrt.find_last_of("\\")+1);
			newTexMap->texMapFileName = addStringToData( (char*)tenmpsrt.c_str() );
		}
		else
			newTexMap->texMapFileName = addStringToData("");

		newTexMap->texMapSlot = mat->GetIGameTextureMap(i)->GetStdMapSlot();
	}
	
	
}


int Max12Exporter::matExist(IGameMaterial* pMat)
{
	bool rt = false;
	std::for_each(std::begin(matVec),std::end(matVec),[&](IGameMaterial* p){
		if (p == pMat)rt = true;
	});
	
	return rt;
}

size_t Max12Exporter::vertexExist(Vertex& v)
{
	v.uv.y = 1 - v.uv.y;

	for (size_t i = 0;i < vertexVec.size();++i)
	{
		if(vertexVec[i].pos.Equals(v.pos) &&
		   vertexVec[i].uv.Equals(v.uv) &&
		   vertexVec[i].normal.Equals(v.normal))
			return i;
	}

	 //add the vertex
	 
	
	vertexVec.push_back(v);
	return vertexVec.size()-1;	
}

void Max12Exporter::ShowAbout( HWND hwnd )
{
}

int Max12Exporter::ExtCount()
{
	return 1;
}

const TCHAR* Max12Exporter::Ext( int /*n*/ )
{
	return _T("N3D");
}

const TCHAR* Max12Exporter::LongDesc()
{
	return _T("My 3dsmax 12 Exporter");
}

const TCHAR* Max12Exporter::ShortDesc()
{
	return _T("Max12Exporter");
}

const TCHAR* Max12Exporter::AuthorName()
{
	return _T("");
}

const TCHAR* Max12Exporter::CopyrightMessage()
{
	return _T("Copyright (C)");
}

const TCHAR* Max12Exporter::OtherMessage1()
{
	return _T("");
}

const TCHAR* Max12Exporter::OtherMessage2()
{
	return _T("");
}

unsigned int Max12Exporter::Version()
{
	return 1;
}

BOOL Max12Exporter::SupportsOptions( int ext, DWORD options )
{
	return TRUE;
}

INT64 Max12Exporter::getObjectByName( const char* objName )
{
	for (size_t i = 0; i < objectVec.size(); ++i)
	{
		char* theName =(char*)stringData[objectVec[i]->objectName];
		if (strcmp(theName,objName) == 0)
		{
			//find it
			return  (i * sizeof(Object));
		}
	}

	return -1;
}

INT64 Max12Exporter::addStringToData( char* str )
{
	size_t len = strlen(str);
	size_t startOffset = stringData.size();
	stringData.resize(stringData.size() + len + 1);
	char* rt = new(&stringData[0] + startOffset) char[len+1];
	strcpy(rt,str);
	return  startOffset;
}

int Max12Exporter::getMaterialIndexing( IGameMaterial* mat )
{
	for (size_t i = 0; i < matVec.size(); ++i)
	{
		if (matVec[i] == mat)
		{
			return (int)i;
		}
	}
	return -1;
}


}